/*-------------------------------------------------------------------------
 *
 * pgxc_ctl.c
 *
 *    Main module of Postgres-XC configuration and operation tool.
 *
 * Copyright (c) 2013 Postgres-XC Development Group
 *
 *-------------------------------------------------------------------------
 */
/*
 *  PXC_CTL  Postgres-XC configurator and operation tool
 *
 *
 * Command line options
 *
 * -c --configuration file : configuration file.  Rerative path
 *							start at $HOME/.pgxc_ctl or homedir if
 *							specified by --home option
 * --home homedir : home directory of pgxc_ctl.  Default is
 *					$HOME/.pgxc_ctl.  You can override this
 *					with PGXC_CTL_HOME environment or option.
 *					Command argument has the highest priority.
 *
 * -v | --verbose: verbose mode.  You can set your default in
 *					pgxc_ctl_rc file at home.
 *
 * --silent: Opposite to --verbose.
 *
 * -V | --version: prints out the version
 *
 * -l | --logdir dir: Log directory.   Default is $home/pgxc_log
 *
 * -L | --logfile file: log file.  Default is the timestamp.
 *					Relative path starts with --logdir.
 *
 */

#include <stdlib.h>
#include <stdio.h>
#include <readline/readline.h>
#include <readline/history.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <getopt.h>

#include "config.h"
#include "variables.h"
#include "pgxc_ctl.h"
#include "bash_handler.h"
#include "signature.h"
#include "pgxc_ctl_log.h"
#include "varnames.h"
#include "do_command.h"
#include "utils.h"

/*
 * Common global variable
 */
char pgxc_ctl_home[MAXPATH + 1];
char pgxc_ctl_bash_path[MAXPATH + 1];
char pgxc_ctl_config_path[MAXPATH + 1];
char progname[MAXPATH + 1];
char* myName;
char* defaultDatabase;
#define versionString "V1.0 for Postgres-XC 1.1"

FILE* inF;
FILE* outF;

static void build_pgxc_ctl_home(char* home);
static void trim_trailing_slash(char* path);
static void startLog(char* path, char* logFileNam);
static void print_version(void);
static void print_help(void);

static void trim_trailing_slash(char* path)
{
    char* curr = path;
    char* last = path;

    while (*curr) {
        last = curr;
        curr++;
    }
    while (last != path) {
        if (*last == '/') {
            last = 0;
            last--;
            continue;
        } else
            return;
    }
}

static void build_pgxc_ctl_home(char* home)
{
    char* env_pgxc_ctl_home = getenv(PGXC_CTL_HOME);
    char* env_home = getenv(HOME); /* We assume this is always available */

    if (home) {
        if (home[0] == '/') {
            /* Absolute path */
            strncpy(pgxc_ctl_home, home, MAXPATH);
            goto set_bash;
        } else {
            /* Relative path */
            trim_trailing_slash(home);
            snprintf(pgxc_ctl_home, MAXPATH, "%s/%s", env_home, home);
            goto set_bash;
        }
    }
    if ((env_pgxc_ctl_home = getenv(PGXC_CTL_HOME)) == NULL) {
        snprintf(pgxc_ctl_home, MAXPATH, "%s/%s", env_home, pgxc_ctl_home_def);
        goto set_bash;
    }
    if (env_pgxc_ctl_home[0] == '/') /* Absoute path */
    {
        strncpy(pgxc_ctl_home, env_pgxc_ctl_home, MAXPATH);
        goto set_bash;
    }
    trim_trailing_slash(env_pgxc_ctl_home);
    if (env_pgxc_ctl_home[0] == '\0' || env_pgxc_ctl_home[0] == ' ' || env_pgxc_ctl_home[0] == '\t') {
        /* Null environment */
        snprintf(pgxc_ctl_home, MAXPATH, "%s/%s", env_home, pgxc_ctl_home_def);
        goto set_bash;
    }
    snprintf(pgxc_ctl_home, MAXPATH, "%s/%s", env_home, home);
    goto set_bash;

set_bash:
    snprintf(pgxc_ctl_bash_path, MAXPATH, "%s/%s", pgxc_ctl_home, PGXC_CTL_BASH);
    /*
     * Create home dir if necessary and change current directory to it.
     */
    {
        struct stat buf;
        char cmd[MAXLINE + 1];

        if (stat(pgxc_ctl_home, &buf) == 0) {
            if (S_ISDIR(buf.st_mode)) {
                Chdir(pgxc_ctl_home, TRUE);
                return;
            } else {
                fprintf(stderr, "%s is not directory.  Check your configurfation\n", pgxc_ctl_home);
                exit(1);
            }
        }
        snprintf(cmd, MAXLINE, "mkdir -p %s", pgxc_ctl_home);
        system(cmd);
        if (stat(pgxc_ctl_home, &buf) == 0) {
            if (S_ISDIR(buf.st_mode)) {
                Chdir(pgxc_ctl_home, TRUE);
                return;
            } else {
                fprintf(stderr, "Creating %s directory failed. Check your configuration\n", pgxc_ctl_home);
                exit(1);
            }
        }
        fprintf(stderr, "Creating directory %s failed. %s\n", pgxc_ctl_home, strerror(errno));
        exit(1);
    }
    return;
}

static void build_configuration_path(char* path)
{
    struct stat statbuf;
    int rr;

    if (path)
        reset_var_val(VAR_configFile, path);
    if (!find_var(VAR_configFile) || !sval(VAR_configFile) || (sval(VAR_configFile)[0] == 0)) {
        /* Default */
        snprintf(pgxc_ctl_config_path, MAXPATH, "%s/%s", pgxc_ctl_home, DEFAULT_CONF_FILE_NAME);
        rr = stat(pgxc_ctl_config_path, &statbuf);
        if (rr || !S_ISREG(statbuf.st_mode)) {
            /* No configuration specified and the default does not apply --> simply ignore */
            elog(ERROR,
                "ERROR: Default configuration file \"%s\" was not found while no configuration file was specified\n",
                pgxc_ctl_config_path);
            pgxc_ctl_config_path[0] = 0;
            return;
        }
    } else if (sval(VAR_configFile)[0] == '/') {
        /* Absolute path */
        strncpy(pgxc_ctl_config_path, sval(VAR_configFile), MAXPATH);
    } else {
        /* Relative path from $pgxc_ctl_home */
        snprintf(pgxc_ctl_config_path, MAXPATH, "%s/%s", pgxc_ctl_home, sval(VAR_configFile));
    }
    rr = stat(pgxc_ctl_config_path, &statbuf);
    if (rr || !S_ISREG(statbuf.st_mode)) {
        if (rr)
            elog(ERROR,
                "ERROR: File \"%s\" not found or not a regular file. %s\n",
                pgxc_ctl_config_path,
                strerror(errno));
        else
            elog(ERROR, "ERROR: File \"%s\" not found or not a regular file", pgxc_ctl_config_path);
    }
    return;
}

static void read_configuration(void)
{
    FILE* conf = NULL;
    char cmd[MAXPATH + 1];

    install_pgxc_ctl_bash(pgxc_ctl_bash_path);
    if (pgxc_ctl_config_path[0])
        snprintf(
            cmd, MAXPATH, "%s --home %s --configuration %s", pgxc_ctl_bash_path, pgxc_ctl_home, pgxc_ctl_config_path);
    else
        snprintf(cmd, MAXPATH, "%s --home %s", pgxc_ctl_bash_path, pgxc_ctl_home);
    elog(NOTICE, "Reading configuration using %s\n", cmd);
    conf = popen(cmd, "r");
    if (conf == NULL) {
        elog(ERROR, "ERROR: Cannot execute %s, %s", cmd, strerror(errno));
        return;
    }
    read_vars(conf);
    fclose(conf);
    uninstall_pgxc_ctl_bash(pgxc_ctl_bash_path);
    elog(INFO, "Finished to read configuration.\n");
}

static void prepare_pgxc_ctl_bash(char* path)
{
    struct stat buf;
    int rc;

    rc = stat(path, &buf);
    if (rc)
        install_pgxc_ctl_bash(path);
    else if (S_ISREG(buf.st_mode))
        return;
    rc = stat(path, &buf);
    if (S_ISREG(buf.st_mode))
        return;
    fprintf(stderr, "Error: caould not install bash script %s\n", path);
    exit(1);
}

static void pgxcCtlMkdir(char* path)
{
    char cmd[MAXPATH + 1];

    snprintf(cmd, MAXPATH, "mkdir -p %s", path);
    system(cmd);
}

static void startLog(char* path, char* logFileNam)
{
    char logFilePath[MAXPATH + 1];

    if (path) {
        trim_trailing_slash(path);
        pgxcCtlMkdir(path);
        if (logFileNam) {
            if (logFileNam[0] == '/') {
                fprintf(stderr, "ERROR: both --logdir and --logfile are specified and logfile was abosolute path.\n");
                exit(1);
            }
            if (path[0] == '/')
                snprintf(logFilePath, MAXPATH, "%s/%s", path, logFileNam);
            else
                snprintf(logFilePath, MAXPATH, "%s/%s/%s", pgxc_ctl_home, path, logFileNam);
            initLog(NULL, logFilePath);
        } else {
            if (path[0] == '/')
                initLog(path, NULL);
            else {
                snprintf(logFilePath, MAXPATH, "%s/%s", pgxc_ctl_home, path);
                initLog(logFilePath, NULL);
            }
        }
    } else {
        if (logFileNam && logFileNam[0] == '/') {
            /* This is used as log file path */
            initLog(NULL, logFileNam);
            return;
        } else {
            snprintf(logFilePath, MAXPATH, "%s/pgxc_log", pgxc_ctl_home);
            pgxcCtlMkdir(logFilePath);
            initLog(logFilePath, NULL);
        }
    }
    return;
}

static void setDefaultIfNeeded(char* name, char* val)
{
    if (!find_var(name) || !sval(name)) {
        if (val)
            reset_var_val(name, val);
        else
            reset_var(name);
    }
}

static void setup_my_env(void)
{
    char path[MAXPATH + 1];
    char* home = NULL;
    FILE* ini_env = NULL;

    char* selectVarList[] = {VAR_pgxc_ctl_home,
        VAR_xc_prompt,
        VAR_verbose,
        VAR_logDir,
        VAR_logFile,
        VAR_tmpDir,
        VAR_localTmpDir,
        VAR_configFile,
        VAR_echoAll,
        VAR_debug,
        VAR_printMessage,
        VAR_logMessage,
        VAR_defaultDatabase,
        VAR_pgxcCtlName,
        VAR_printLocation,
        VAR_logLocation,
        NULL};

    ini_env = fopen("/etc/pgxc_ctl", "r");
    if (ini_env) {
        read_selected_vars(ini_env, selectVarList);
        fclose(ini_env);
    }
    if ((home = getenv("HOME"))) {
        snprintf(path, MAXPATH, "%s/.pgxc_ctl", getenv("HOME"));
        if ((ini_env = fopen(path, "r"))) {
            read_selected_vars(ini_env, selectVarList);
            fclose(ini_env);
        }
    }
    /*
     * Setup defaults
     */
    snprintf(path, MAXPATH, "%s/pgxc_ctl", getenv("HOME"));
    setDefaultIfNeeded(VAR_pgxc_ctl_home, path);
    setDefaultIfNeeded(VAR_xc_prompt, "PGXC ");
    snprintf(path, MAXPATH, "%s/pgxc_ctl/pgxc_log", getenv("HOME"));
    setDefaultIfNeeded(VAR_logDir, path);
    setDefaultIfNeeded(VAR_logFile, NULL);
    setDefaultIfNeeded(VAR_tmpDir, "/tmp");
    setDefaultIfNeeded(VAR_localTmpDir, "/tmp");
    setDefaultIfNeeded(VAR_configFile, "pgxc_ctl.conf");
    setDefaultIfNeeded(VAR_echoAll, "n");
    setDefaultIfNeeded(VAR_debug, "n");
    setDefaultIfNeeded(VAR_printMessage, "info");
    setDefaultIfNeeded(VAR_logMessage, "info");
    setDefaultIfNeeded(VAR_pgxcCtlName, DefaultName);
    myName = Strdup(sval(VAR_pgxcCtlName));
    setDefaultIfNeeded(VAR_defaultDatabase, DefaultDatabase);
    defaultDatabase = Strdup(sval(VAR_defaultDatabase));
    setDefaultIfNeeded(VAR_printLocation, "n");
    setDefaultIfNeeded(VAR_logLocation, "n");
}

int main(int argc, char* argv[])
{
    char* configuration = NULL;
    char* infile = NULL;
    char* outfile = NULL;
    char* verbose = NULL;
    int version_opt = 0;
    char* logdir = NULL;
    char* logfile = NULL;
    char* home = NULL;
    int help_opt = 0;

    int c;

    static struct option long_options[] = {{"configuration", required_argument, 0, 'c'},
        {"silent", no_argument, 0, 1},
        {"verbose", no_argument, 0, 'v'},
        {"version", no_argument, 0, 'V'},
        {"logdir", required_argument, 0, 'l'},
        {"logfile", required_argument, 0, 'L'},
        {"home", required_argument, 0, 2},
        {"infile", required_argument, 0, 'i'},
        {"outfile", required_argument, 0, 'o'},
        {"help", no_argument, 0, 'h'},
        {0, 0, 0, 0}};

    strcpy(progname, argv[0]);
    init_var_hash();

    while (1) {
        int option_index = 0;

        c = getopt_long(argc, argv, "i:o:c:vVl:L:h", long_options, &option_index);

        if (c == -1)
            break;
        switch (c) {
            case 1:
                verbose = "n";
                break;
            case 2:
                if (home)
                    free(home);
                home = strdup(optarg);
                break;
            case 'i':
                if (infile)
                    free(infile);
                infile = strdup(optarg);
                break;
            case 'o':
                if (outfile)
                    free(outfile);
                outfile = strdup(optarg);
                break;
            case 'v':
                verbose = "y";
                break;
            case 'V':
                version_opt = 1;
                break;
            case 'l':
                if (logdir)
                    free(logdir);
                logdir = strdup(optarg);
                break;
            case 'L':
                if (logfile)
                    free(logfile);
                logfile = strdup(optarg);
                break;
            case 'c':
                if (configuration)
                    free(configuration);
                configuration = strdup(optarg);
                break;
            case 'h':
                help_opt = 1;
                break;
            default:
                fprintf(stderr, "Invalid optin value, received code 0%o\n", c);
                exit(1);
        }
    }
    if (version_opt || help_opt) {
        if (version_opt)
            print_version();
        if (help_opt)
            print_help();
        exit(0);
    }
    setup_my_env(); /* Read $HOME/.pgxc_ctl */
    build_pgxc_ctl_home(home);
    if (infile)
        reset_var_val(VAR_configFile, infile);
    if (logdir)
        reset_var_val(VAR_logDir, logdir);
    if (logfile)
        reset_var_val(VAR_logFile, logfile);
    startLog(sval(VAR_logDir), sval(VAR_logFile));
    prepare_pgxc_ctl_bash(pgxc_ctl_bash_path);
    build_configuration_path(configuration);
    read_configuration();
    check_configuration();
    /*
     * Setop output
     */
    if (outfile) {
        elog(INFO, "Output file: %s\n", outfile);
        if ((outF = fopen(outfile, "w")))
            dup2(fileno(outF), 2);
        else
            elog(ERROR, "ERROR: Cannot open output file %s, %s\n", outfile, strerror(errno));
    } else
        outF = stdout;
    /*
     * Startup Message
     */
    elog(NOTICE, "   ******** PGXC_CTL START ***************\n\n");
    elog(NOTICE, "Current directory: %s\n", pgxc_ctl_home);
    /*
     * Setup input
     */
    if (infile) {
        elog(INFO, "Input file: %s\n", infile);
        inF = fopen(infile, "r");
        if (inF == NULL) {
            elog(ERROR, "ERROR: Cannot open input file %s, %s\n", infile, strerror(errno));
            exit(1);
        }
    } else
        inF = stdin;
        /*
         * If we have remaing arguments, they will be treated as a command to do.  Do this
         * first, then handle the input from input file specified by -i option.
         * If it is not found, then exit.
         */
    if (optind < argc) {
        char orgBuf[MAXLINE + 1];
        char wkBuf[MAXLINE + 1];
        orgBuf[0] = 0;
        while (optind < argc) {
            strncat(orgBuf, argv[optind++], MAXLINE);
            strncat(orgBuf, " ", MAXLINE);
        }
        strncpy(wkBuf, orgBuf, MAXLINE);
        do_singleLine(orgBuf, wkBuf);
        if (infile)
            do_command(inF, outF);
    } else
        do_command(inF, outF);
    exit(0);
}

static void print_version(void)
{
    printf("Pgxc_ctl %s\n", versionString);
}

static void print_help(void)
{
    printf("pgxc_ctl [option ...] [command]\n"
           "option:\n"
           "   -c or --configuration conf_file: Specify configruration file.\n"
           "   -v or --verbose: Specify verbose output.\n"
           "   -V or --version: Print version and exit.\n"
           "   -l or --logdir log_directory: specifies what directory to write logs.\n"
           "   -L or --logfile log_file: Specifies log file.\n"
           "   --home home_direcotry: Specifies pgxc_ctl work director.\n"
           "   -i or --infile input_file: Specifies inptut file.\n"
           "   -o or --outfile output_file: Specifies output file.\n"
           "   -h or --help: Prints this message and exits.\n"
           "For more deatils, refer to pgxc_ctl reference manual included in\n"
           "postgres-xc reference manual.\n");
}
